"""
===============================
@author     : yaoruiqi

@Time       : 2023/3/27 9:56

@version    : V

@introduce  :

@change     : 
===============================
"""
import asyncio
from time import time, localtime, strftime
from xml.etree.ElementTree import Element, SubElement, tostring

from django.core.cache import cache
from loguru import logger

from api.baidu_api import baidu_api, package_baidu_api
from api.public_methods import get_access_token
from api.wechat_api import template_message
from app_subscription_account.models import SubscriptionUser
from aps_task.aps_task import DailyPush


class ReplyMessage:

    def __init__(self, to_user_openid: str, origin_id: str):
        """
        回复消息初始化
        @param to_user_openid: 接受用户的openid
        @param origin_id: 程序原始id
        """
        self.to_user_openid = to_user_openid
        self.origin_id = origin_id

    def reply_text(self, message: str):
        root = Element("xml")
        SubElement(root, "ToUserName").text = self.to_user_openid
        SubElement(root, "FromUserName").text = self.origin_id
        SubElement(root, "CreateTime").text = f"{time()}"
        SubElement(root, "MsgType").text = "text"
        SubElement(root, "Content").text = message
        return tostring(root, encoding="unicode", method="xml")


class BeachburgMessage:

    def __init__(self, msg_body: dict):
        self.openid = msg_body['FromUserName']
        self.message_type = msg_body['MsgType']
        self.origin_id = msg_body['ToUserName']
        self.msg_body = msg_body

    def reply_introduction(self):
        cache_key = f'reply_introduction:{self.origin_id}:{self.openid}'
        cache_data = cache.get(cache_key)
        if cache_data:
            return ''
        else:
            cache.set(cache_key, 1, 60 * 60 * 8)
            # todo: 比奇堡公众号指令存入数据库
            introduction = "欢迎来到比奇堡公众号~~~"
            return ReplyMessage(to_user_openid=self.openid, origin_id=self.origin_id).reply_text(message=introduction)

    def content_identification(self):
        """
        根据消息类型进行处理
        @rtype: object
        """
        if self.message_type == 'text':  # 文本消息类型
            # todo: 优化语义识别
            msg_content = self.msg_body['Content']
            logger.debug(f'未识别语义: {msg_content}')
            return self.reply_introduction()
        else:
            logger.error(f'暂不支持回复此消息类型：{self.message_type}')
            return '暂不支持回复此消息类型'


class TipsMessage:

    def __init__(self, msg_body: dict):
        self.openid = msg_body['FromUserName']
        self.message_type = msg_body['MsgType']
        self.origin_id = msg_body['ToUserName']
        self.msg_body = msg_body

    def reply_introduction(self):
        cache_key = f'reply_introduction:{self.origin_id}:{self.openid}'
        cache_data = cache.get(cache_key)
        if cache_data:
            return ''
        else:
            cache.set(cache_key, 1, 60 * 60 * 8)
            # todo: 推送公众号指令存入数据库
            introduction = '\n'.join([
                '欢迎使用每日推送公众号~~~',
                '下面是当前可使用的指令：',
                '1. 每日推送'
            ])
            return ReplyMessage(to_user_openid=self.openid, origin_id=self.origin_id).reply_text(message=introduction)

    def send_personal_daily_tips(self):
        user_obj = SubscriptionUser.objects.get(openid=self.openid)
        location_adcode = user_obj.location_adcode if user_obj.location_adcode else '110101'
        asyncio.run(DailyPush().manager({self.openid: location_adcode}))
        return ''

    def update_user_location(self):
        """
        更新用户地理位置
        @return:
        @rtype:
        """
        try:
            # 半小时可更新一次地理位置
            if 'Longitude' in self.msg_body and 'Latitude' in self.msg_body:
                lon = self.msg_body['Longitude']
                lat = self.msg_body['Latitude']
                cache_key = f'user_location:{self.origin_id}:auto_{self.openid}'
            elif 'Location_X' in self.msg_body and 'Location_Y' in self.msg_body:
                lon = self.msg_body['Location_Y']
                lat = self.msg_body['Location_X']
                cache_key = f'user_location:{self.origin_id}:click_{self.openid}'
            else:
                raise AssertionError('地理位置获取失败')

            if not cache.get(cache_key):
                position_info = baidu_api.geocoder(lon=lon, lat=lat)
                assert position_info, '经纬度获取地理详细信息失败'
                adcode = position_info.get('addressComponent').get('adcode')
                formatted_address = position_info.get('formatted_address')
                user_obj = SubscriptionUser.objects.get(openid=self.openid)
                user_obj.location_adcode = adcode
                user_obj.location_name = formatted_address
                user_obj.save()
                template_message.send_template_message(
                    access_token=get_access_token(service_name='subscription_tips'),
                    user_open_id=self.openid,
                    template_name='消息通知',
                    time=strftime("%Y-%m-%d %H:%M:%S", localtime()),
                    msg=f'地理位置已更新至【{formatted_address}】'
                )
                cache.set(cache_key, 1, 60 * 30)
            return ''
        except SubscriptionUser.DoesNotExist:
            return False
        except AssertionError as _:
            return False
        except Exception as e:
            logger.exception(f'更新用户地理位置失败，{e}')
            return False

    def send_weather_forecast(self):
        """
        获取天气预报
        @return:
        @rtype:
        """
        try:
            user_obj = SubscriptionUser.objects.get(openid=self.openid)
            location_adcode = user_obj.location_adcode if user_obj.location_adcode else '110101'
            _, weather_info_dict = package_baidu_api.get_weather(adcode=location_adcode)

            now_ts = time()
            day_list = [strftime("%Y-%m-%d", localtime(now_ts + i * 86400)) for i in range(5)]
            if day_list[-1] not in weather_info_dict:
                _, weather_info_dict = package_baidu_api.get_weather(adcode=location_adcode, reset_cache=True)

            now_weather = weather_info_dict['now']
            day0_weather = weather_info_dict[day_list[0]]
            day1_weather = weather_info_dict[day_list[1]]
            day2_weather = weather_info_dict[day_list[2]]
            day3_weather = weather_info_dict[day_list[3]]
            day4_weather = weather_info_dict[day_list[4]]

            data = {
                'now_weather': now_weather['text'],
                'now_temp': now_weather['temp'],
                'now_wind': f"{now_weather['wind_dir']} {now_weather['wind_class']}",

                'd0': day_list[0],
                'w0': day0_weather['text_day'],
                'w1': day0_weather['text_night'],
                't0': day0_weather['high'],
                't1': day0_weather['low'],
                'n0': f"{day0_weather['wd_day']}{day0_weather['wc_day']}",
                'n1': f"{day0_weather['wd_night']}{day0_weather['wc_night']}",

                'd1': day_list[1],
                'w2': day1_weather['text_day'],
                'w3': day1_weather['text_night'],
                't2': day1_weather['high'],
                't3': day1_weather['low'],
                'n2': f"{day1_weather['wd_day']}{day1_weather['wc_day']}",
                'n3': f"{day1_weather['wd_night']}{day1_weather['wc_night']}",

                'd2': day_list[2],
                'w4': day2_weather['text_day'],
                'w5': day2_weather['text_night'],
                't4': day2_weather['high'],
                't5': day2_weather['low'],
                'n4': f"{day2_weather['wd_day']}{day2_weather['wc_day']}",
                'n5': f"{day2_weather['wd_night']}{day2_weather['wc_night']}",

                'd3': day_list[3],
                'w6': day3_weather['text_day'],
                'w7': day3_weather['text_night'],
                't6': day3_weather['high'],
                't7': day3_weather['low'],
                'n6': f"{day3_weather['wd_day']}{day3_weather['wc_day']}",
                'n7': f"{day3_weather['wd_night']}{day3_weather['wc_night']}",

                'd4': day_list[4],
                'w8': day4_weather['text_day'],
                'w9': day4_weather['text_night'],
                't8': day4_weather['high'],
                't9': day4_weather['low'],
                'n8': f"{day4_weather['wd_day']}{day4_weather['wc_day']}",
                'n9': f"{day4_weather['wd_night']}{day4_weather['wc_night']}",
            }

            template_message.send_template_message(
                access_token=get_access_token(service_name='subscription_tips'),
                user_open_id=self.openid,
                template_name='天气预报',
                **data
            )
            return ''
        except SubscriptionUser.DoesNotExist:
            return False
        except AssertionError as _:
            return False
        except Exception as e:
            logger.exception(f'获取天气预报失败，{e}')
            return False

    def handler_click_event(self, key: str):
        if key == 'btn_daily_push':
            return self.send_personal_daily_tips()
        elif key == 'btn_weather_forecast':
            logger.debug('weather click')
            return self.send_weather_forecast()
        elif key == 'btn_function_introduction':
            logger.debug('introduction click')
            return ReplyMessage(to_user_openid=self.openid, origin_id=self.origin_id).reply_text(
                message='功能介绍功能敬请期待')
        elif key == 'btn_feedback':
            logger.debug('feedback click')
            return ReplyMessage(to_user_openid=self.openid, origin_id=self.origin_id).reply_text(
                message='问题反馈功能敬请期待')
        else:
            logger.error(f'暂不支持回复此CLICK类型事件：{self.message_type}')
            return ''

    def content_identification(self):
        """
        根据消息类型进行处理
        @rtype: object
        """
        if self.message_type == 'text':  # 文本消息类型
            # todo: 优化语义识别
            if (msg_content := self.msg_body['Content']) == '每日推送':
                return self.send_personal_daily_tips()
            else:
                logger.debug(f'未识别语义: {msg_content}')
                return self.reply_introduction()
        elif self.message_type == 'event':
            if (event_name := self.msg_body['Event']) == 'LOCATION':
                # 地理位置消息类型
                return self.update_user_location()
            elif event_name == 'TEMPLATESENDJOBFINISH':
                # 模板消息发送结果通知
                return ''
            elif event_name == 'CLICK':
                return self.handler_click_event(key=self.msg_body['EventKey'])
            else:
                logger.error(f'暂不支持回复此事件类型 event_name:{event_name}')
                return '暂不支持回复此事件类型'
        elif self.message_type == 'location':
            return self.update_user_location()
        else:
            logger.error(f'暂不支持回复此消息类型：{self.message_type}')
            return '暂不支持回复此消息类型'


def MessageHandler(app_name: str, **kwargs):
    """
    消息处理工厂调用
    @param app_name: 小程序名
    @param kwargs: 消息体返回的内容
    """
    if app_name == '每日消息公众号':
        return TipsMessage(kwargs).content_identification()
    elif app_name == '比奇堡的熊孩子公众号':
        return BeachburgMessage(kwargs).content_identification()
    else:
        raise AssertionError(f'消息来源app_name错误  {app_name}')
